#pragma once

#include <vex/core/config.h>
#include <vex/policy/protect_policy.h>
#include <vex/policy/empty_base.h>
#include <memory>
#include <limits>
#include <new>

namespace vex { namespace core { namespace implementation {

    template< class TClass, class TClassTraits, class TBase = policy::empty_base >
    class allocator_policy {
        VEX_PROTECT_POLICY(allocator_policy);
    public:
        typedef typename TClassTraits::value_type    value_type;
        typedef typename TClassTraits::pointer       pointer;
        typedef typename TClassTraits::const_pointer const_pointer;
        typedef typename TClassTraits::reference     reference;
        typedef typename TClassTraits::const_reference const_reference;
        typedef typename TClassTraits::size_type size_type;
        typedef typename TClassTraits::difference_type difference_type;

        pointer address(reference p_arg) const 
        { 
            return std::addressof(p_arg); 
        }

        const_pointer address(const_reference p_arg) const 
        { 
            return p_arg;
        }

        pointer allocate(size_type p_count, const_pointer p_pointer_hint = nullptr) 
        {
            if (void* t_pointer = static_cast<TClass const*>(this)
                ->get()
                ->allocate(sizeof(value_type) * p_count, std::alignment_of<value_type>::value, p_pointer_hint))
            {
                return static_cast<pointer>(t_pointer);
            } 

            throw std::bad_alloc();
        }

        void deallocate(pointer p_pointer, size_type p_count) 
        {
            return static_cast<TClass const*>(this)
                ->get()
                ->deallocate(p_pointer, sizeof(value_type) * p_count, std::alignment_of<value_type>::value);
        }

        size_type max_size() const 
        { 
            return std::numeric_limits<size_type>::max() / sizeof(value_type);
        }

#ifdef VEX_HAS_CPP_11_FEATURE_SET_3
        template<class ...TArgs>
        void construct(pointer p, TArgs&&... p_args)
        {
            new(p) value_type(std::forward<TArgs>(p_args)...);
        }
#else
        void construct(pointer p) 
        { 
            new(p) value_type; 
        }

        template<class TArg1>
        void construct(pointer p, TArg1&& p_arg1) 
        { 
            new(p) value_type(std::forward<TArg1>(p_arg1)); 
        }

        template<class TArg1, class TArg2>
        void construct(pointer p, TArg1&& p_arg1, TArg2&& p_arg2) 
        { 
            new(p) value_type(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2)); 
        }

        template<class TArg1, class TArg2, class TArg3>
        void construct(pointer p, TArg1&& p_arg1, TArg2&& p_arg2, TArg3&& p_arg3) 
        { 
            new(p) value_type(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3)); 
        }

        template<class TArg1, class TArg2, class TArg3, class TArg4>
        void construct(pointer p, TArg1&& p_arg1, TArg2&& p_arg2, TArg3&& p_arg3, TArg4&& p_arg4) 
        { 
            new(p) value_type(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3),
                std::forward<TArg4>(p_arg4)); 
        }
#endif

        void destroy(pointer p) 
        { 
            p->~value_type(); 
        }
    };
}}}